import React, {
  FC,
  HTMLAttributes,
  useState,
  FunctionComponentElement,
  createContext,
  useMemo,
  useRef,
  KeyboardEvent,
  useEffect,
} from 'react';
import classNames from 'classnames';
import Input, { InputProps } from '../Input';
import { OptionProps } from './option';
import { OptGroupProps } from './OptGroup';
import Transition from '../Transition';
import useClickOutside from '../../hooks/useClickOutside';
import Type from '../../utils/validator';

type SelectMode = 'multiple' | 'tags' | undefined;
interface SelectProps
  extends Pick<InputProps, 'append'>,
    Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
  defaultValue?: string | string[];
  children: React.ReactNode;
  placeholder: string;
  onChange: (value: string) => void;
  mode?: SelectMode;
}

interface IselectContext {
  valueArr: string[];
  onSelect: (value: string) => void;
  lighlightValue: string;
}

export const SelectContext = createContext<IselectContext>({} as IselectContext);

export const Select: FC<SelectProps> = (props) => {
  const { children, placeholder, onChange, mode = undefined, defaultValue = [] } = props;

  // select选中值
  const [valueArr, setValueArr] = useValueArray(mode, defaultValue);

  const optionComponentsProps = useRef<OptionProps[]>([]);

  const [lighlightValue, setLighlightValue] = useLighlightValue(optionComponentsProps);

  const [showOptions, setShowOptions] = useState<boolean>(false);

  const [isFocus, setFocus] = useState<boolean>(false);

  // 点击空白处关闭选项
  const componentRef = useRef<HTMLDivElement>(null);
  useClickOutside(componentRef, () => {
    setShowOptions(false);
    setFocus(false);
  });

  /**
   * @description 选中option组件后的回调
   * @param value  option组件的值
   */
  const optionSelect = (value: string = '') => {
    setValueArr(value);
    setTimeout(() => {
      setShowOptions(false);
    }, 300);
    if (onChange) onChange(value);
  };

  const contextValue = createContextValue(valueArr, optionSelect, lighlightValue);

  // 方向键上下移动  回车键 Esc健处理
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (!showOptions && e.key !== 'Escape') {
      setShowOptions(true);
      return;
    }

    switch (e.key) {
      case 'Enter':
        optionSelect(lighlightValue);
        break;
      case 'ArrowUp':
        setLighlightValue('subtract');
        break;
      case 'ArrowDown':
        setLighlightValue('add');
        break;
      case 'Escape':
        setShowOptions(false);
        break;
      default:
        break;
    }
  };

  return (
    <div className="weiwei-ui-select-selector" ref={componentRef} onFocus={() => setFocus(true)}>
      <div className="weiwei-ui-select-search" onClick={() => setShowOptions(true)}>
        <div className="weiwei-select-input-wrapper">
          <input
            className="weiwei-select-input-inner"
            type="text"
            readOnly
            defaultValue={valueArr.join(',')}
            onKeyDown={handleKeyDown}
          />
        </div>
      </div>
      <Transition in={showOptions} animation="zoom-in-top" timeout={3000}>
        <div className="weiwei-ui-select-options">
          <SelectContext.Provider value={contextValue}>
            {renderChildren(children, optionComponentsProps)}
          </SelectContext.Provider>
        </div>
      </Transition>
    </div>
  );
};

type setValueArrType = (value: string) => void;
type useValueArrayType = (
  mode: SelectMode,
  defaultValue: string | string[],
) => [string[], setValueArrType];
/**
 * @description 创建默认值和修改函数
 * @param mode  组件单选或多选模式
 * @param defaultValue 用户设定的默认值
 * @returns [值 , 设置值的函数]
 */
const useValueArray: useValueArrayType = (mode = undefined, defaultValue) => {
  defaultValue = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
  const [valueArr, baseSetValue] = useState<string[]>(defaultValue);
  const setValueArr: setValueArrType = (value) => {
    if (mode) {
      // 多选处理
      const valueItem = valueArr.find((valueItem) => valueItem === value);
      if (valueItem) {
        baseSetValue(valueArr.filter((deletValue) => deletValue !== valueItem));
      } else {
        baseSetValue([...valueArr, value]);
      }
    } else {
      // 单选
      baseSetValue([value]);
    }
  };
  return [valueArr, setValueArr];
};

// 渲染子元素
type renderChildrenType = (
  children: React.ReactNode,
  optionComponentsProps: React.MutableRefObject<OptionProps[]>,
) => JSX.Element[] | null | undefined;
/**
 * @description 给子元素加一层div包裹
 * @param children
 * @param optionComponentsProps
 * @returns 被div包装后的子组件
 */
const renderChildren: renderChildrenType = (children, optionComponentsProps) => {
  optionComponentsProps.current.length = 0; // 收集所有 option 子组件 props
  collectOptionComponents(children, optionComponentsProps);
  return React.Children.map(children, (child, index) => {
    const childElement = child as FunctionComponentElement<OptionProps | OptGroupProps>;
    const { displayName } = childElement.type;
    if (['Option', 'OptGroup'].includes(displayName as string)) {
      return <div className="weiwei-ui-select-options-item">{childElement}</div>;
    } else {
      console.error('Warning: Select has a child which is not a Option or OptionGroup component');
    }
  });
};

type collectOptionComponentsType = (
  children: React.ReactNode,
  optionComponentsProps: React.MutableRefObject<OptionProps[]>,
) => void;
/**
 * @description 收集option子组件props
 * @param children react组件的 children
 * @param optionComponentsProps 收集option组件props
 * @returns undefind
 */
const collectOptionComponents: collectOptionComponentsType = (children, optionComponentsProps) => {
  if (Type.isString(children)) return;

  if (Type.isObject(children)) {
    const childElement = children as FunctionComponentElement<OptionProps | OptGroupProps>;
    if (childElement.type.displayName === 'OptGroup') {
      collectOptionComponents(childElement.props.children, optionComponentsProps);
    }
    if (childElement.type.displayName === 'Option') {
      const childElement = children as FunctionComponentElement<OptionProps>;
      optionComponentsProps.current.push(childElement.props);
    }
    return;
  }
  if (Type.isArray(children)) {
    React.Children.map(children, (itemChild) => {
      collectOptionComponents(itemChild, optionComponentsProps);
    });
  }
};

type createContextValueType = (
  valueArr: string[],
  optionSelect: (value: string) => void,
  lighlightValue: string,
) => IselectContext;
/**
 * @description 创建 Context value
 * @param valueArr select 框选中的值
 * @param optionSelect
 * @param lighlightValue
 * @returns
 */
const createContextValue: createContextValueType = (valueArr, optionSelect, lighlightValue) => {
  return useMemo(
    () => ({
      valueArr,
      onSelect: optionSelect,
      lighlightValue,
    }),
    [valueArr.join(), lighlightValue],
  );
};

// 设置高亮
type setLighlightValueType = (action: 'add' | 'subtract') => void;
type useLighlightValue = (
  optionComponentsProps: React.MutableRefObject<OptionProps[]>,
) => [string, setLighlightValueType];
const useLighlightValue: useLighlightValue = (optionComponentsProps) => {
  const [highlightIndex, setHighlightIndex] = useState<number>(0);
  const [lighlightValue, setValue] = useState<string>('');

  // 初始化高亮的值给子组件做激活样式用
  useEffect(() => {
    if (optionComponentsProps.current[highlightIndex])
      setValue(optionComponentsProps.current[highlightIndex]?.value);
  }, []);

  // 修正选中高亮位置
  useEffect(() => {
    if (!lighlightValue || !optionComponentsProps.current.length) return;
    const valueIndex = optionComponentsProps.current.findIndex(
      (item) => item.value === lighlightValue,
    );
    if (valueIndex !== -1) {
      if (valueIndex !== highlightIndex) {
        setHighlightIndex(valueIndex);
      }
    } else {
      const currentValue = optionComponentsProps.current[highlightIndex]?.value;
      if (currentValue !== lighlightValue) {
        setValue(currentValue);
      }
    }
  });

  const setLighlightValue: setLighlightValueType = (action) => {
    let index = action === 'add' ? highlightIndex + 1 : highlightIndex - 1;
    if (optionComponentsProps.current.length === 0) return;
    if (index <= -1) index = optionComponentsProps.current.length - 1;
    if (index >= optionComponentsProps.current.length) index = 0;
    setHighlightIndex(index);
    setValue(optionComponentsProps.current[index]?.value);
  };
  return [lighlightValue, setLighlightValue];
};

Select.defaultProps = {};

export default Select;
